//
//  Reminder.m
//  test1
//
//  Created by Nearsoft User on 7/31/12.
//  Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//

#define COMPLETE    -1
#define UNCOMPLETE  -2

#import "Connection.h"
#import "TestFlight.h"

static sqlite3_stmt *init_statement = nil;
static sqlite3_stmt *edit_statement = nil;
static sqlite3_stmt *delete_statement = nil;
static sqlite3_stmt *insert_statement = nil;

@implementation Connection

@synthesize reminder;

- (id)init
{
	if (self = [super init])
    {
        [self copyDatabaseIfNeeded];
        [self initializeDatabase];
        dateString = [[DateStrings alloc] init];

        return self;
    }
    else return nil;
}

#pragma mark - Reminder Methods
/**
 * Method name: saveReminderIntoDatabase
 * Description: Calls the appropriate method to insert a new reminder or edit it
 * Parameters: aReminder: the reminder to be inserted or edited
 */
-(void)saveReminderIntoDatabase:(Reminder *)aReminder
{
    // If the reminder index is 0, the reminder to be inserted into the database and the returned index will be assigned to it
    if (aReminder.primaryKey == 0)
        aReminder.primaryKey = [self insertNewReminderIntoDatabase:aReminder];
    else
        [self editReminderFromDatabase:aReminder];    // If the index is not 0, the reminder will be edited
}

/**
 * Method name: insertNewReminderIntoDatabase
 * Description: inserts a new reminder into the database
 * Parameters: aReminder: the reminder that is going to be inserted into the database
 */
- (NSInteger)insertNewReminderIntoDatabase:(Reminder *) aReminder;
{
    const char *sql = "INSERT INTO todo (title, date, notes, repeat, complete) VALUES(?, ?, ?, ?, 0)";

    // Prepares the statement to be executed on the database and if there is an error show it
    if (sqlite3_prepare_v2(database, sql, -1, &insert_statement, NULL) != SQLITE_OK)
        NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));

    // Replace the "?" symbols from the statement with the reminder values
    sqlite3_bind_int(insert_statement, 4, [aReminder repeat]);
    sqlite3_bind_text(insert_statement, 3, [[aReminder notes] UTF8String], -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(insert_statement, 2, [[dateString stringFromDate:[aReminder date]] UTF8String], -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(insert_statement, 1, [[aReminder title] UTF8String], -1, SQLITE_TRANSIENT);

    // Executes the statement
    int success = sqlite3_step(insert_statement);
    sqlite3_reset(insert_statement);

    // If there isn't any error, the method retuns the index of the new inserted reminder
    if (success != SQLITE_ERROR)
        return sqlite3_last_insert_rowid(database);

    // Show an error message if there is any
    NSAssert1(0, @"Error: failed to insert into the database with message '%s'.", sqlite3_errmsg(database));
    return -1;

    #if !(TARGET_OS_IPHONE)
        [TestFlight passCheckpoint:@"Reminder Inserted"];
    #endif
}

/**
 * Method name: editReminderFromDatabase
 * Description: Edits a reminder from the database
 * Parameters: reminderToEdit: the reminder data that will replace the old reminder
 */
- (void)editReminderFromDatabase:(Reminder *)reminderToEdit
{
    const char *sql = "UPDATE todo SET title = ? , date = ?, notes = ?, repeat = ?, complete = 0 WHERE pk = ?";

    // Prepares the statement to be executed on the database and if there is an error show it
    if (sqlite3_prepare_v2(database, sql, -1, &edit_statement, NULL) != SQLITE_OK)
        NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));

    // Replace the "?" symbols from the statement with the reminder values
	sqlite3_bind_int(edit_statement, 5, reminderToEdit.primaryKey);
    sqlite3_bind_int(edit_statement, 4, [reminderToEdit repeat]);
    sqlite3_bind_text(edit_statement, 3, [[reminderToEdit notes] UTF8String], -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(edit_statement, 2, [[dateString stringFromDate:[reminderToEdit date]] UTF8String], -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(edit_statement, 1, [[reminderToEdit title] UTF8String], -1, SQLITE_TRANSIENT);

    // Executes the statement
	int success = sqlite3_step(edit_statement);

    // If there is an error show it
	if (success != SQLITE_DONE)
		NSAssert1(0, @"Error: failed to edit from database with message '%s'.", sqlite3_errmsg(database));

    sqlite3_reset(edit_statement);
}

/**
 * Method name: deleteReminderFromDatabase
 * Description: Deletes a reminder from the database
 * Parameters: pk: the index of the reminder that is going to be deleted from the database
 */
-(void) deleteReminderFromDatabase:(NSInteger)pk
{
	if (delete_statement == nil)  // If the statement haven't been initialized yet, do it
    {
		const char *sql = "DELETE FROM todo WHERE pk=?";

        // Prepares the statement to be executed on the database and if there is an error show it
		if (sqlite3_prepare_v2(database, sql, -1, &delete_statement, NULL) != SQLITE_OK)
			NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));
	}
    
    // Replace the "?" symbol from the statement with the reminder index
	sqlite3_bind_int(delete_statement, 1, pk);

    // Executes the statement
	int success = sqlite3_step(delete_statement);

    // If there is an error show it
    if (success != SQLITE_DONE)
		NSAssert1(0, @"Error: failed to save priority with message '%s'.", sqlite3_errmsg(database));

	sqlite3_reset(delete_statement);

    #if !(TARGET_OS_IPHONE)
        [TestFlight passCheckpoint:@"Reminder Deleted"];
    #endif
}

/**
 * Method name: getAllReminders
 * Description: Get all the reminders from the database to be shown in RemindersViewController
 * Parameters: done: defines if the array will be filled with be filled with completed remainders or uncompleted ones
 */
- (NSMutableArray *)getAllReminders:(BOOL)done
{
    done ? [self getRemindersBase:COMPLETE] : [self getRemindersBase:UNCOMPLETE];

    NSMutableArray *reminders = [[NSMutableArray alloc] init];

    // Executes the statement While the database returns a row
    while (sqlite3_step(init_statement) == SQLITE_ROW)
    {
        Reminder *newReminder = [[Reminder alloc] init];

        // Get the reminder values from the row
        newReminder.primaryKey = sqlite3_column_int(init_statement, 0);
        newReminder.title = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 1)];
        newReminder.date = [dateString stringToDate:[NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 2)]];
        newReminder.notes = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 3)];
        newReminder.repeat = sqlite3_column_int(init_statement, 4);
        newReminder.complete = sqlite3_column_int(init_statement, 5);

        // Add the reminder to an array
        [reminders addObject:newReminder];
    }

    // Deletes the prepared statement
    sqlite3_finalize(init_statement);

    // Return the array with the reminders
    return reminders;
}

/**
 * Method name: getSingleReminder
 * Description: Get a reminder from the database to be shown in RemindersViewController
 * Parameters: pk: The index of the reminder to be shown
 */
- (Reminder *)getSingleReminder:(NSInteger)pk
{
    [self getRemindersBase:pk];

    // Executes the statement
    int success = sqlite3_step(init_statement);

    // If there is an error show it
    if (!success)
		NSAssert1(0, @"Error: failed to get from database with message '%s'.", sqlite3_errmsg(database));

    if (success != SQLITE_ROW)
        return nil;

    Reminder *singleReminder = [[Reminder alloc] init];

    // Get the reminder values from the database
    singleReminder.primaryKey = sqlite3_column_int(init_statement, 0);
    singleReminder.title = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 1)];
    singleReminder.date = [dateString stringToDate:[NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 2)]];
    singleReminder.notes = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 3)];
    singleReminder.repeat = sqlite3_column_int(init_statement, 4);
    singleReminder.complete = sqlite3_column_int(init_statement, 5);

    // Deletes the prepared statement
    sqlite3_finalize(init_statement);

    // Return the reminder
    return singleReminder;
}

/**
 * Method name: getRemindersBase
 * Description: Defines and prepares every SELECT statement
 * Parameters: pk: determines the SELECT statement that will be used
 */
- (void)getRemindersBase:(NSInteger)pk
{
    // The string that will be used as the SQLite statement
    const char *sql;

    switch (pk)
    {
        case UNCOMPLETE:sql = "SELECT * FROM todo where complete = 0";
                        break;
        case COMPLETE:sql = "SELECT * FROM todo where complete = 1";
                      break;
        default:sql = "SELECT * FROM todo where pk = ?";
                break;
    }

    // Prepares the statement to be executed on the database and if there is an error show it
    if (sqlite3_prepare_v2(database, sql, -1, &init_statement, NULL) != SQLITE_OK)
        NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));

    // Replace the "?" symbol from the statement with the pk parameter
    sqlite3_bind_int(init_statement, 1, pk);
}

/**
 * Method name: updateComplete
 * Description: Changes the complete propertie of the reminder from the datbase
 * Parameters: pk: the index of the reminder data that will updated
 */
- (void)updateReminderCompleteFromDatabase:(NSInteger)pk
{
    const char *sql = "UPDATE todo SET complete = 1 WHERE pk = ?";

    // Prepares the statement to be executed on the database and if there is an error show it
    if (sqlite3_prepare_v2(database, sql, -1, &edit_statement, NULL) != SQLITE_OK)
        NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));

    // Replace the "?" symbols from the statement with the reminder values
	sqlite3_bind_int(edit_statement, 1, pk);

    // Executes the statement
	int success = sqlite3_step(edit_statement);

    // If there is an error show it
	if (success != SQLITE_DONE)
		NSAssert1(0, @"Error: failed to edit from database with message '%s'.", sqlite3_errmsg(database));

    sqlite3_reset(edit_statement);

    #if !(TARGET_OS_IPHONE)
        [TestFlight passCheckpoint:@"Reminder Completed"];
    #endif
}

/**
 * Method name: updateComplete
 * Description: Changes the complete propertie of the reminder from the datbase
 * Parameters: pk: the index of the reminder data that will updated
 */
- (void)updateReminderDateFromDatabase:(NSInteger)pk
{
    const char *sql = "UPDATE todo SET date = ? WHERE pk = ?";

    // Prepares the statement to be executed on the database and if there is an error show it
    if (sqlite3_prepare_v2(database, sql, -1, &edit_statement, NULL) != SQLITE_OK)
        NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));

    Reminder *aReminder = [[Reminder alloc] init];

    aReminder = [self getSingleReminder:pk];
    aReminder.date = [dateString addDate:aReminder.repeat date:aReminder.date];

    // Replace the "?" symbols from the statement with the reminder values
    sqlite3_bind_text(edit_statement, 1, [[dateString stringFromDate:aReminder.date] UTF8String], -1, SQLITE_TRANSIENT);
	sqlite3_bind_int(edit_statement, 2, pk);

    // Executes the statement
	int success = sqlite3_step(edit_statement);

    // If there is an error show it
	if (success != SQLITE_DONE)
		NSAssert1(0, @"Error: failed to edit from database with message '%s'.", sqlite3_errmsg(database));

    sqlite3_reset(edit_statement);
    
    #if !(TARGET_OS_IPHONE)
        [TestFlight passCheckpoint:@"Date of Reminder Updated"];
    #endif
}

#pragma mark - Database Methods
/**
 * Method name: initializeDatabase
 * Description: Makes the connection with the database
 * Parameters:
 */
- (void)initializeDatabase
{
    // Locates the database
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"todo.sqlite"];

    // If the connection can be made
    if(sqlite3_open([path UTF8String], &database) == SQLITE_OK)
        NSLog(@"%@", path);
    else
    {
        // Closes the connection and show the error
        sqlite3_close(database);
        NSAssert1(0, @"Failed to open database with message '%s'.", sqlite3_errmsg(database));
    }
}

/**
 * Method name: copyDatabaseIfNeeded
 * Description: Copies the database
 * Parameters:
 */
- (void)copyDatabaseIfNeeded
{
    // Locates the database
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error; 
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];

    // Prepares the path where the copy database would be
    NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:@"todo.sqlite"];

    // If the database already exist success will be true
    BOOL success = [fileManager fileExistsAtPath:writableDBPath];

    if (success)    // If the database already exists, exits the method 
        return;

    // Copies the database
    NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"todo.sqlite"];
    success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];

    // If the database cannot be copied show the error
    if (!success)
        NSAssert1(0, @"Failed to create writable database file with message '%@'.", [error localizedDescription]);
}

@end